#include "zd_sec_mgr.h"
#include "mfg_Stack.h"
#include "mfg.h"
#include "mfg_coord.h"
#include "zstackapi.h"
#include "util_timer.h"

/* macro */
#define MFG_CMD_SIMPLE_DESC   0x04
#define MFG_CMD_ACTIVE_EP     0x05

/* typedef */
typedef struct
{
  void*  next;
  uint16_t dest;
  uint8_t *pSeqNum;
  uint8_t cmdId;
  uint8_t cmdSize;
  uint8_t cmdData[];
}mfgZdpQueue_t;

typedef struct
{
  void *next;
  uint8_t mac[8];
  uint8_t timeSec;
}mfgWhiteList_t;

typedef struct
{
  void* next;
  uint8_t  mac[8];
  uint16_t nodeId;
  uint16_t parent;
  uint8_t  type;
  uint8_t  timeSec;
  uint8_t  retry;
  uint8_t  seq;
  void (*cb)(void*);
}mfgNodeList_t;

typedef struct
{
  void* next;
  uint8_t  mac[8];
  uint16_t nodeId;
  uint8_t  ep;
  uint8_t  cmdSeq;
  uint8_t  timeSec;
  uint8_t  retry;
  uint8_t  inCount;
  uint16_t *inCluster;
  uint8_t  outCount;
  uint16_t *outCluster;
  uint16_t cluster[25];
}mfgDeviceList_t;

/* variable */
Clock_Struct mfgPeriodClkStruct;
uint8_t mfgZdpSendDelaySec = COORD_ZDP_SEND_DELAY_SEC;

mfgWhiteList_t *mfgWhiteListHead = NULL;
mfgNodeList_t *mfgNodeListHead = NULL;
mfgDeviceList_t *mfgDeviceListHead = NULL;
mfgZdpQueue_t *mfgZdpQueueHead = NULL;

ZStatus_t mfgDeviceValidateCallback( uint16 nwkAddr, uint8* extAddr,
                                      uint16 parentAddr, uint8 secure, uint8 devStatus );
void MFG_TcJoinNodeCB(void*);
void MFG_DevAnnceNodeCB(void*);

/* static function */
static void mfgWlistRefresh(void);
static void mfgWlistPush(mfgWhiteList_t* item);
static mfgWhiteList_t* mfgWlistFind(uint8_t *mac);

//Node List Function
void mfgNodeRefresh(void);
void mfgNodePush(mfgNodeList_t* item);
mfgNodeList_t* mfgNodeFindByMac(uint8_t* mac);
mfgNodeList_t* mfgNodeFindByNodeId(uint16_t nodeId);

//Device List Function
void mfgDeviceRefresh(void);
void mfgDevicePush(mfgDeviceList_t* item);
mfgDeviceList_t* mfgDeviceFind(uint16_t nodeId, uint8_t ep);

//Zdp queue function
mfgZdpQueue_t* mfgZdpPop(void);
void mfgZdpPush(mfgZdpQueue_t* item);

//local func
void mfgPeriodTimeoutCB(UArg a0);

//extern function
void MFG_InitCallback(void)
{
  pZDSecMgrDeviceValidateCallback = mfgDeviceValidateCallback;
  Timer_construct(
                  &mfgPeriodClkStruct,
                  mfgPeriodTimeoutCB,
                  1000,
                  0, FALSE, 0);
}

/*
* @fn          MFG_PeriodTick
*
* @brief       called in mfg task per second
*
* @param       none
*
* @return      none
*
* */
void MFG_PeriodCallback(void)
{
  mfgWlistRefresh();
  mfgNodeRefresh();
  mfgDeviceRefresh();
  if(mfgZdpSendDelaySec)
  {
    mfgZdpSendDelaySec --;
  }
  else
  {
    MFG_ZdpReqPoll();
    mfgZdpSendDelaySec = COORD_ZDP_SEND_DELAY_SEC;
  }
}

/*
* @fn          mfgDeviceValidateCallback
*
* @brief       point to pZDSecMgrDeviceValidateCallback in zd_sec_mgr.h
*
* @param       ZDSecMgrDevice_t in zd_sec_mgr.c
*
* @return      ZSuccess or ZNwkUnknownDevice
*
* */
ZStatus_t mfgDeviceValidateCallback( uint16 nwkAddr, uint8* extAddr,
                                     uint16 parentAddr, uint8 secure, uint8 devStatus )
{
  uint8_t  found = false;
  uint16_t keyNvIndex = APSME_SearchTCLinkKeyEntry(extAddr,&found,NULL);
  if(found == TRUE)
  {
    return ZSuccess;
  }
  //lookup white list
  if( MFG_WhiteListCheck(extAddr))
  {
    return ZSuccess;
  }
  //out put denied extAddr
  mfgMsgHdr_t* pMsg = MFG_CreatMsg(Mfg_Device_Denied,8);
  if(pMsg)
  {
    memcpy(pMsg->data, extAddr, 8);
    MFG_SendMsg(pMsg);
  }
  return ZNwkUnknownDevice;
}

void mfgPeriodTimeoutCB(UArg a0)
{
  MFG_setEvent(MFG_TaskId(), MFG_PERIOD_EVENT);
}
/*
* @fn          MFG_WhiteListAdd
*
* @brief       add a ext-address into whitelist
*
* @param       mac - extAddress
*
* @return      message pointer
*
* */
bool MFG_WhiteListAdd( uint8_t *mac )
{
  mfgWhiteList_t *newItem = MFG_malloc( sizeof(mfgWhiteList_t) );
  if(newItem)
  {
    newItem->next = NULL;
    newItem->timeSec = 240;
    memcpy(newItem->mac, mac, 8);
    mfgWlistPush(newItem);
    return true;
  }
  return false;
}

/*
* @fn          MFG_WhiteListCheck
*
* @brief       check white list by ext-address
*
* @param       mac - extAddress
*
* @return      True of False
*
* */
bool MFG_WhiteListCheck(uint8_t *mac)
{
  mfgWhiteList_t* item = mfgWlistFind(mac);
  if(item)
  {
    item->timeSec = 0;
    return true;
  }
  return false;
}

/*
* @fn          MFG_TcJoinInd
*
* @brief       message zstackmsg_CmdIDs_ZDO_TC_DEVICE_IND
*
* @param       nodeId - short Address
*              mac - ext Address
*              parant - parent short address
* 
* @return      None
*
* */
void MFG_TcJoinInd(uint16_t nodeId, uint8_t *mac, uint16_t parent)
{
  mfgNodeList_t* item = mfgNodeFindByMac(mac);
  if(item == NULL)
  {
    item = MFG_malloc(sizeof(mfgNodeList_t));
    if(item)
    {
      memcpy(item->mac, mac, 8);
      item->nodeId = nodeId;
      item->parent = parent;
      item->timeSec = NODE_TIME_TC;
      item->retry = 0;
      item->type = 0;
      item->next = NULL;
      item->cb = MFG_TcJoinNodeCB;
      mfgNodePush(item);
    }
  }
  else
  {
      item->timeSec = NODE_TIME_TC;
  }
}

/*
* @fn          MFG_DeviceAnnounceInd
*
* @brief       message zstackmsg_CmdIDs_ZDO_TC_DEVICE_IND
*
* @param       nodeId - short Address
*              mac - ext Address
*              parant - parent short address
* 
* @return      None
*
* */
void MFG_DeviceAnnounceInd(uint16_t nodeId, uint8_t *mac, uint8_t nodeType)
{
  mfgNodeList_t* item = mfgNodeFindByMac(mac);
  if(item)
  {
    if(item->nodeId == nodeId)
    {
      item->cb = MFG_DevAnnceNodeCB;
      item->type = nodeType;
      item->retry = 4;
      item->timeSec = 0;
    }
  }
}

/*
* @fn          MFG_ActiveEpInd
*
* @brief       message zstackmsg_CmdIDs_ZDO_ACTIVE_EP_RSP
*
* @param       nodeId - short Address
*              seq - rsp trans seq
*              epCnt - endpoint count
*              epArray - endpoint list
* 
* @return      None
*
* */
bool MFG_ActiveEpInd(uint16_t nodeId, uint8_t seq, uint8_t status, uint8_t epCnt, uint8_t* epList)
{
  mfgNodeList_t* find = mfgNodeFindByNodeId(nodeId);
  if(find->seq == seq)
  {
    uint8_t n;
    if(status == ZSuccess)
    {
      for( n = 0; n < epCnt; n++ )
      {
        //add simple desc
        mfgDeviceList_t *item = MFG_malloc(sizeof(mfgDeviceList_t));
        if(item)
        {
          item->nodeId = find->nodeId;
          item->
          item->ep = epList[n];
          mfgDevicePush(item);
        }
      }
    }
    find->retry = 0;
    find->timeSec = 0;
    return true;
  }
  return false;
}

/*
* @fn          MFG_SimpleDescInd
*
* @brief       message zstackmsg_CmdIDs_ZDO_SIMPLE_DESC_RSP
*
* @param       
* 
* @return      None
*
* */
bool MFG_SimpleDescInd(uint16_t nodeId, uint8_t seq, uint8_t ep, uint16_t profId, uint16_t devId, uint8_t devVer,
                       uint8_t inNum, uint16_t *inCluster, uint8_t outNum, uint16_t *outCluster)
{
  return false;
}

/*
  ZDP send queue
*/
bool MFG_ActiveEpQuery(uint16_t nodeId, uint8_t *pSeq)
{
  mfgZdpQueue_t* item = MFG_malloc(sizeof(mfgZdpQueue_t));
  if(item)
  {
    item->cmdId = MFG_CMD_ACTIVE_EP;
    item->cmdSize = 0;
    item->dest = nodeId;
    item->pSeqNum = pSeq;
    return true;
  }
  return false;
}

bool MFG_SimpleDescQuery(uint16_t nodeId, uint8_t *pSeq, uint8_t ep)
{
  mfgZdpQueue_t* item = MFG_malloc(sizeof(mfgZdpQueue_t) + 1);
  if(item)
  {
    item->cmdId = MFG_CMD_SIMPLE_DESC;
    item->cmdSize = 1;
    item->dest = nodeId;
    item->pSeqNum = pSeq;
    item->cmdData[0] = ep;
    return true;
  }
  return false;
}

/* ZDP poll */
void MFG_ZdpReqPoll(void)
{
  mfgZdpQueue_t* item = mfgZdpPop();
  if(item)
  {
    switch(item->cmdId)
    {
    case MFG_CMD_SIMPLE_DESC:
      {
        
      }
      break;
      
    case MFG_CMD_ACTIVE_EP:
      {
        zstack_zdoActiveEndpointReq_t req = {0};
        req.dstAddr = item->dest;
        req.nwkAddrOfInterest = item->dest;
        req.extParam.afCnfCB = NULL;
        req.extParam.cnfParam = NULL;
        Zstackapi_ZdoActiveEndpointReq(MFG_TaskId(), &req);
        *(item->pSeqNum) = req.extParam.seqNum;
      }
      break;
    }
  }
}


void MFG_TcJoinNodeCB(void* p)
{
  mfgNodeList_t* item = p;
  
  
}

void MFG_DevAnnceNodeCB(void* p)
{
  mfgNodeList_t* item = p;
  
  if(MFG_ActiveEpQuery(item->nodeId, &item->seq))
  {
    
  }
  else
  {
    item->timeSec = 0;
    
  }
}

/*
*        Local Function
*/

/*
* @fn          mfgWlistRefresh
*
* @brief       refresh whitelist
*
* @param       none
*
* @return      none
*
* */
void mfgWlistRefresh(void)
{
  mfgWhiteList_t* poll = mfgWhiteListHead;
  mfgWhiteList_t* pre = NULL;
  while(poll)
  {
    void *next = poll->next;
    if(poll->timeSec == 0)
    {
      mfgWhiteList_t* free = poll;
      if(free == mfgWhiteListHead)
      {
        mfgWhiteListHead = free->next;
      }
      else
      {
        pre->next = free->next;
      }
      MFG_free(free);
    }
    else
    {
      poll->timeSec -= 1;
      pre = poll;
    }
    poll = next;
  }
}

/*
* @fn          mfgWlistPush
*
* @brief       add whitelist item into queue
*
* @param       none
*
* @return      none
*
* */
static void mfgWlistPush(mfgWhiteList_t* item)
{
  if(mfgWhiteListHead == NULL)
  {
    mfgWhiteListHead = item;
  }
  else
  {
    mfgWhiteList_t* tail = mfgWhiteListHead;
    while(tail->next != NULL)
    {
      tail = tail->next;
    }
    tail->next = item;
  }
}

/*
* @fn          mfgWlistFind
*
* @brief       lookup whitelist item by extAddr
*
* @param       mac - extAddr
*
* @return      whitelist item
*
* */
static mfgWhiteList_t* mfgWlistFind(uint8_t *mac)
{
  mfgWhiteList_t* find = mfgWhiteListHead;
  while(find)
  {
    if(0 == memcmp(mac, find->mac, 8))
    {
      return find;
    }
    find = find->next;
  }
  return NULL;
}

/*
* @fn          mfgNodeRefresh
*
* @brief       Node List Refresh
*
* @param       None
*
* @return      None
*
* */
void mfgNodeRefresh(void)
{
  mfgNodeList_t* poll = mfgNodeListHead;
  mfgNodeList_t* pre = NULL;
  while(poll)
  {
    void *next = poll->next;
    if((poll->timeSec == 0)&&(poll->retry == 0))
    {
      mfgNodeList_t* free = poll;
      if(free == mfgNodeListHead)
      {
        mfgNodeListHead = free->next;
      }
      else
      {
        pre->next = free->next;
      }
      MFG_free(free);
    }
    else
    {
      if(poll->timeSec > 0)
      {
        poll->timeSec -= 1;
      }
      else if(poll->retry > 0)
      {
        poll->retry -= 1;
        if(poll->cb)
        {
          poll->cb(poll);
        }
      }
      pre = poll;
    }
    poll = next;
  }
}

/*
* @fn          mfgNodePush
*
* @brief       Add new item into Node List
*
* @param       item - new item of Node List
*
* @return      None
*
* */
void mfgNodePush(mfgNodeList_t* item)
{
  if(item)
  {
    if(mfgNodeListHead == NULL)
    {
      mfgNodeListHead = item;
    }
    else
    {
      mfgNodeList_t* tail = mfgNodeListHead;
      while(tail->next)
      {
        tail = tail->next;
      }
      tail->next = item;
    }
  }
}

/*
* @fn          mfgNodeFindByMac
*
* @brief       lookup Node List by ext-Address
*
* @param       mac - ext Address
*
* @return      Node List item
*
* */
mfgNodeList_t* mfgNodeFindByMac(uint8_t* mac)
{
  mfgNodeList_t* find = mfgNodeListHead;
  while(find)
  {
    if(0 == memcmp(find->mac,mac,8))
    {
      return find;
    }
    find = find->next;
  }
  return NULL;
}

/*
* @fn          mfgNodeFindByNodeId
*
* @brief       lookup Node List by short address
*
* @param       nodeId - short address
*
* @return      Node List item
*
* */
mfgNodeList_t* mfgNodeFindByNodeId(uint16_t nodeId)
{
  mfgNodeList_t* find = mfgNodeListHead;
  while(find)
  {
    if(find->nodeId == nodeId)
    {
      return find;
    }
    find = find->next;
  }
  return NULL;
}

/*
* @fn          mfgDeviceRefresh
*
* @brief       device List Refresh
*
* @param       None
*
* @return      None
*
* */
void mfgDeviceRefresh(void)
{
  mfgDeviceList_t* poll = mfgDeviceListHead;
  mfgDeviceList_t* pre = NULL;
  while(poll)
  {
    void *next = poll->next;
    if((poll->timeSec == 0)&&(poll->retry == 0))
    {
      mfgDeviceList_t* free = poll;
      if(free == mfgDeviceListHead)
      {
        mfgDeviceListHead = free->next;
      }
      else
      {
        pre->next = free->next;
      }
      MFG_free(free);
    }
    else
    {
      if(poll->timeSec > 0)
      {
        poll->timeSec -= 1;
      }
      else if(poll->retry > 0)
      {
        poll->retry -= 1;
      }
      pre = poll;
    }
    poll = next;
  }
}

/*
* @fn          mfgDevicePush
*
* @brief       Add new item into Device List
*
* @param       item - new item of Device List
*
* @return      None
*
* */
void mfgDevicePush(mfgDeviceList_t* item)
{
  if(item)
  {
    item->next = NULL;
    if(mfgDeviceListHead == NULL)
    {
      mfgDeviceListHead = item;
    }
    else
    {
      mfgDeviceList_t* tail = mfgDeviceListHead;
      while(tail->next)
      {
        tail = tail->next;
      }
      tail->next = item;
    }
  }
}

/*
* @fn          mfgDeviceFind
*
* @brief       lookup Device List by short address and endpoint
*
* @param       nodeId - short address
*
* @return      Device List item
*
* */
mfgDeviceList_t* mfgDeviceFind(uint16_t nodeId, uint8_t ep)
{
  mfgDeviceList_t* item = mfgDeviceListHead;
  while(item)
  {
    if((item->nodeId == nodeId) && (item->ep == ep))
    {
      return item;
    }
    item = item->next;
  }
  return NULL;
}

/*
* @fn          mfgZdpPop
*
* @brief       pop Zdp queue
*
* @param       none
*
* @return      zdp queue item
*
* */
mfgZdpQueue_t* mfgZdpPop(void)
{
  if(mfgZdpQueueHead)
  {
    mfgZdpQueue_t* item = mfgZdpQueueHead;
    mfgZdpQueueHead = item->next;
    item->next = NULL;
    return item;
  }
  return NULL;
}

/*
* @fn          mfgZdpPush
*
* @brief       push Zdp queue
*
* @param       zdp queue item
*
* @return      none
*
* */
void mfgZdpPush(mfgZdpQueue_t* item)
{
  if(item == NULL)
  {
    return;
  }
  item->next = NULL;
  if(mfgZdpQueueHead == NULL)
  {
    mfgZdpQueueHead = item;
  }
  else
  {
    mfgZdpQueue_t* tail = mfgZdpQueueHead;
    while(tail->next)
    {
      tail = tail->next;
    }
    tail->next = item;
  }
}